from flask import request, render_template, flash, redirect, url_for, session
import pandas as pd
from prophet import Prophet
from datetime import datetime, timedelta
import numpy as np
import io
from collections import defaultdict
import re
import matplotlib
import matplotlib.pyplot as plt
import base64
import random
import os
from werkzeug.utils import secure_filename
import time

def single_predict_view():
    product_info = None
    table = None
    metrics = None
    plot_url = None
    params = {'days': 30, 'stock_inventory': 100, 'procurement_transit': 20,'lead_time': 15, 'return_time': 10, 'safety_factor': 1.2,
              'only_return_rate': 0.6, 'shipment_return_rate': 0.5,'incremental_activity':0,'procurement_arrived_date':datetime.now().strftime('%Y-%m-%d'),
              'activity_impact':0.0, 'activity_start':datetime.now().strftime('%Y-%m-%d'),'activity_end':datetime.now().strftime('%Y-%m-%d'),'peak_start':datetime.now().strftime('%Y-%m-%d'),
              'decline_start':datetime.now().strftime('%Y-%m-%d')
            }
    show_result = False
    daily_results = None
    summary = None
    upload_dir = os.path.join(os.path.dirname(__file__), '../uploads')
    os.makedirs(upload_dir, exist_ok=True)
    uploaded_filename = session.get('uploaded_file_display') if 'uploaded_file_display' in session else None
    # 展示原始文件名而不是带时间戳的
    display_filename = uploaded_filename.split('_', 1)[1] if uploaded_filename and '_' in uploaded_filename else uploaded_filename
    if request.method == 'POST':
        file = request.files.get('file')
        days = int(request.form.get('days', 30))
        stock_inventory = int(request.form.get('stock_inventory', 0))
        procurement_transit = int(request.form.get('procurement_transit', 0))
        incremental_activity = int(request.form.get('incremental_activity', 0))
        lead_time = int(request.form.get('lead_time', 15))
        return_time = int(request.form.get('return_time', 10))
        safety_factor = float(request.form.get('safety_factor', 1.2))
        only_return_rate = float(request.form.get('only_return_rate', 0.0))
        shipment_return_rate = float(request.form.get('shipment_return_rate', 0.0))
        activity_impact = float(request.form.get('activity_impact', 0.0))
        # 处理采购到货日期为空的情况
        # procurement_arrived_date_str = request.form.get('procurement_arrived_date', '')
        # if not procurement_arrived_date_str:
        #     procurement_arrived_date = datetime.strptime('1949-10-01', '%Y-%m-%d')
        # else:
        #     procurement_arrived_date = datetime.strptime(procurement_arrived_date_str, '%Y-%m-%d')
        procurement_arrived_date = date_none(request.form.get('procurement_arrived_date', ''))
        #处理上传数据的活动开始日期
        # activity_start_str = request.form.get('activity_start', '')
        # if not activity_start_str:
        #     activity_start = datetime.strptime('1949-10-01', '%Y-%m-%d')
        # else:
        #     activity_start = datetime.strptime(activity_start_str, '%Y-%m-%d')
        activity_start = date_none(request.form.get('activity_start', ''))
        # 处理上传数据的活动结束日期
        # activity_end_str = request.form.get('activity_end', '')
        # if not activity_end_str:
        #     activity_end = datetime.strptime('1949-10-01', '%Y-%m-%d')
        # else:
        #     activity_end = datetime.strptime(activity_end_str, '%Y-%m-%d')
        activity_end = date_none(request.form.get('activity_end', ''))
        # 爆发期开始日期
        peak_start = date_none(request.form.get('peak_start', ''))
        # 衰减期开始日期
        decline_start = date_none(request.form.get('decline_start', ''))

        # stock_start_date = request.form.get('stock_start_date')
        params = {'days': days, 'stock_inventory': stock_inventory, 'procurement_transit': procurement_transit,'lead_time': lead_time, 'return_time': return_time,
                  'safety_factor': safety_factor, 'only_return_rate': only_return_rate,'shipment_return_rate': shipment_return_rate,'incremental_activity':incremental_activity,
                  'procurement_arrived_date': procurement_arrived_date.strftime('%Y-%m-%d'),'activity_impact':activity_impact,'activity_start':activity_start.strftime('%Y-%m-%d'),
                  'activity_end':activity_end.strftime('%Y-%m-%d'),'peak_start':peak_start.strftime('%Y-%m-%d'),'decline_start':decline_start.strftime('%Y-%m-%d')}
        # current_stock=stock_inventory + procurement_transit  #当前总库存 = 现货库存+采购在途
        current_stock = stock_inventory
        print("current_stock=",current_stock)
        return_rate = (1-only_return_rate) * shipment_return_rate+only_return_rate    # 综合退款率=（1-仅退款率）*发货退款率+仅退款率
        # 文件上传与缓存逻辑
        filename = None
        use_cached_file = request.form.get('use_cached_file')

        if use_cached_file and 'uploaded_file' in session:
            # 备货按钮点击，强制使用缓存文件
            filename = session['uploaded_file']
            save_path = os.path.join(upload_dir, filename)
            if not os.path.exists(save_path):
                flash('缓存的文件已丢失，请重新上传')
                return redirect(request.url)
        elif file and file.filename:
            # 有新文件上传，保存为唯一文件名并更新缓存
            orig_filename = secure_filename(file.filename)
            timestamp = int(time.time())
            filename = f"{timestamp}_{orig_filename}"
            save_path = os.path.join(upload_dir, filename)
            file.save(save_path)
            session['uploaded_file'] = filename
            session['uploaded_file_display'] = file.filename  # 存原始文件名用于显示
        elif 'uploaded_file' in session:
            # 没有新文件，使用缓存的文件
            filename = session['uploaded_file']
            save_path = os.path.join(upload_dir, filename)
            if not os.path.exists(save_path):
                flash('缓存的文件已丢失，请重新上传')
                return redirect(request.url)
        else:
            # 既没有新文件也没有缓存文件
            flash('请上传文件')
            return redirect(request.url)
        # 读取文件
        try:
            if filename.endswith('.csv'):
                df = pd.read_csv(save_path)
                df = pd.DataFrame(df)
                product_id = get_product_id(filename)
                item_id = get_item_id(filename)
            elif filename.endswith(('.xlsx','.xls')):
                df = pd.read_excel(save_path)
                df = pd.DataFrame(df)
                product_id = get_product_id(filename)
                item_id = get_item_id(filename)
            else:
                flash('仅支持CSV或Excel文件')
                return redirect(request.url)
        except Exception as e:
            flash(f'文件读取失败: {e}')
            return redirect(request.url)
        if df is None:
            flash('文件读取失败，未能获得有效数据')
            return redirect(request.url)
        # 字段标准化
        if isinstance(df, pd.DataFrame):
            if '统计日期' in df.columns and '支付件数' in df.columns:
                df = df.loc[:, ['统计日期', '支付件数']]
                df.columns = ['ds', 'y']
            elif '时间' in df.columns and '支付件数' in df.columns:
                df = df.loc[:, ['时间', '支付件数']]
                df.columns = ['ds', 'y']
            elif '日期' in df.columns and '销售数量' in df.columns:
                df = df.loc[:, ['日期', '销售数量']]
                df.columns = ['ds', 'y']
            elif 'ds' in df.columns and 'y' in df.columns:
                df = df.loc[:, ['ds', 'y']]
            else:
                flash('文件需包含"统计日期"和"支付件数"字段')
                return redirect(request.url)
        else:
            flash('文件读取失败，未能获得有效数据')
            return redirect(request.url)
        df = preprocess_data(df)
        print('df=',df)
        df_copy = df.copy()

        # 3. 创建日期范围筛选条件
        date_mask = (df['ds'] >= activity_start) & (df['ds'] <= activity_end)
        df.loc[date_mask, 'y'] = df.loc[date_mask, 'y'] * (1-activity_impact)

        if df is None:
            flash('数据预处理失败')
            return redirect(request.url)
        # 商品基本信息
        product_info = {
            '商品名称': request.form.get('product_name', '商品名称'),
            '商品ID': product_id,
            '货号ID':item_id,
            '数据区间': f"{df['ds'].min().strftime('%Y-%m-%d')} ~ {df['ds'].max().strftime('%Y-%m-%d')}",
            '总记录数': len(df)
        }
        print('product_info=',product_info)
        max_ds = df['ds'].max().strftime('%Y-%m-%d')
        # 计算历史数据的最大最小值
        max_val = df['y'].max()
        print('max_val=',max_val)
        min_val = max(df['y'].min(), 1)  # 避免零值

        # 使用逻辑增长，设置上限为历史最大值的倍数
        df['cap'] = max_val
        df['floor'] = 0
        # Prophet训练
        try:
            model = Prophet(
                # 季节性设置
                yearly_seasonality=False,
                weekly_seasonality=False,
                daily_seasonality=False,

                # 趋势和变化点设置 - 增强局部趋势捕捉
                changepoint_prior_scale=0.15,  # 提高灵敏度以捕捉更多变化点
                changepoint_range=0.95,  # 允许在数据后期设置变化点
                n_changepoints=15,  # 增加变化点数量

                # 节假日设置
                # holidays_prior_scale=5.0,
                # holidays=holidays,

                # 其他重要参数
                seasonality_mode='additive',
                uncertainty_samples=1000,
                mcmc_samples=0,

                # 新增参数以增强局部趋势捕捉
                growth='logistic',  # 使用线性增长（默认）
                seasonality_prior_scale=0.001,  # 降低季节性影响，突出趋势


            )

            # model.add_country_holidays(country_name='CN')
            model.fit(df)
            # 预测期扩展以覆盖备货周期和退货周期
            future = model.make_future_dataframe(periods=days + max(lead_time, return_time) * 2)
            # future = model.make_future_dataframe(periods=days)
            # ====== 新增：计算周期阶段划分 ======
            # 计算每个阶段的长度（使用整数除法处理不能被均分的情况）
            # rising_days = days // 3
            # peak_days = days // 3
            # decline_days = days - rising_days - peak_days

            # 设置阶段边界
            # rising_end = rising_days
            # peak_end = rising_days + peak_days

            # 阶段需求统计
            phase_demand = {
                'rising': 0,
                'peak': 0,
                'decline': 0
            }

            # 初始化阶段名称
            phase_names = {
                'rising': '上升期',
                'peak': '爆发期',
                'decline': '衰退期'
            }
            # ====== 阶段计算结束 ======

            if future is not None and 'ds' in future.columns:
                future = future[future['ds'] > max_ds]  #去除历史数据
                print("future", future)
                # 获取关键日期点
                start_date = future['ds'].min()  # 未来数据开始日期
                end_date = future['ds'].max()    #未来数据结束日期
                # rising_end_date = start_date + pd.Timedelta(days=rising_days)
                # peak_end_date = rising_end_date + pd.Timedelta(days=peak_days)
                # print("peak_end_date", peak_end_date)
                # decline_end_date = peak_end_date + pd.Timedelta(days=decline_days+1)
                # print("decline_end_date", decline_end_date)
            else:
                flash('未来数据生成失败')
                return redirect(request.url)

            # 创建时间区间掩码
            peak_period = (future['ds'] >= peak_start) & (future['ds'] <= decline_start)
            decline_period = (future['ds'] > decline_start) & (future['ds'] <= end_date)
            other_period = future['ds'] < peak_start



            # 生成 [1, 2] 范围内的随机小数（包含1和2）
            # 根据时间段设置cap值
            # future.loc[other_period, 'cap'] = max_val * random.uniform(1, 1.5)
            # future.loc[peak_period, 'cap'] = max_val * random.uniform(2, 2.5)
            # future.loc[decline_period, 'cap'] = max_val * random.uniform(0.3, 0.7)
            # 上升期
            future.loc[other_period, 'cap'] = max_val * np.random.uniform(0.6, 1.4, other_period.sum())
            # 爆发期
            future.loc[peak_period, 'cap'] = max_val * np.random.uniform(1.3, 1.8, peak_period.sum())
            # 衰退期
            future.loc[decline_period, 'cap'] = max_val * np.random.uniform(0.4, 0.6, decline_period.sum())


            # 统一设置floor
            future['floor'] = 0
            # 爆发期
            future.loc[peak_period, 'floor'] = min_val
            forecast = model.predict(future)
            # forecast_future = forecast[forecast['ds'] > datetime.now()].copy()
            forecast_future = forecast[forecast['ds'] > max_ds].copy()

            #预测出的需求与退货率相乘
            # forecast_future['yhat'] = forecast_future['yhat']*(1-return_rate)

            #退货数
            # return_goods = forecast_future['yhat']*return_rate

            # print("forecast_future", forecast_future)

            if forecast_future.empty:
                flash('无法生成未来预测')
                return redirect(request.url)
            # 补货模拟
            daily_results = []
            stock_level = float(current_stock)


            incoming_deliveries = defaultdict(int)
            # print('incoming_deliveries', incoming_deliveries)
            return_deliveries = defaultdict(int)
            # print('return_deliveries', return_deliveries)

            full_forecast_dict = forecast_future.set_index('ds')['yhat'].to_dict()
            sim_dates = forecast_future['ds'].head(days)
            # predicted_demand = incremental_activity
            # return_goods_sum = 0

            for i, date in enumerate(sim_dates):
                # ====== 新增：确定当前日期所处的阶段 ======
                if date < peak_start:
                    current_phase = 'rising'
                elif date < decline_start:
                    current_phase = 'peak'
                else:
                    current_phase = 'decline'
                # ====== 阶段判断结束 ======
                return_goods = return_deliveries.pop(date.date(), 0)
                # return_goods_sum += return_goods
                stock_level += return_goods
                delivery_arrived = incoming_deliveries.pop(date.date(), 0)
                if date == procurement_arrived_date :
                    delivery_arrived = delivery_arrived+procurement_transit
                    procurement_transit = 0
                elif procurement_arrived_date < date == start_date:
                    stock_level =stock_level+ procurement_transit
                    procurement_transit = 0
                stock_level += delivery_arrived
                stock_before_sale = stock_level
                stock_on_order = sum(incoming_deliveries.values()) + sum(return_deliveries.values()) + procurement_transit
                # stock_on_order = sum(incoming_deliveries.values())


                predicted_demand_all = max(0, np.round(full_forecast_dict.get(date, 0)) )

                predicted_demand = predicted_demand_all

                # 实际需求
                if stock_before_sale > 0:
                    real_demand = predicted_demand-(predicted_demand*only_return_rate)
                else:
                    real_demand = predicted_demand-(predicted_demand*only_return_rate)*0.5
                # 退货数
                return_qty = predicted_demand_all * return_rate
                if return_qty >0:  # and stock_before_sale > 0
                    return_arrival_date = date + timedelta(days=return_time)
                    return_deliveries[return_arrival_date.date()] += return_qty
                stock_after_sale = stock_before_sale - predicted_demand
                phase_demand[current_phase] += predicted_demand
                lead_time_window_dates = pd.to_datetime([date + timedelta(days=d) for d in range(0, days - i )])
                # print("date:", date)
                # print("lead_time_window_dates:", lead_time_window_dates)
                all_window_dates = pd.to_datetime([date + timedelta(days=d) for d in range(0, days -i)])
                return_goods_sum = sum(max(0, np.round(full_forecast_dict.get(d, 0))) for d in all_window_dates)*return_rate
                print("return_rate",return_rate)
                print("return_goods_sum", return_goods_sum)
                demand_during_lead_time = sum(max(0, np.round(full_forecast_dict.get(d, 0))) for d in lead_time_window_dates)
                print("demand_during_lead_time", demand_during_lead_time)
                safety_stock_for_lead_time = demand_during_lead_time * (safety_factor - 0.0)
                #售卖周期总共的销量
                reorder_point = demand_during_lead_time - return_goods_sum
                if incremental_activity != 0:
                    reorder_point = reorder_point + incremental_activity*(1-return_rate)
                    incremental_activity = 0
                # if stock_after_sale < 0:
                #     stock_after_sale = stock_after_sale*(1-return_rate)
                inventory_position = stock_before_sale + stock_on_order


                order_qty = max(0, np.ceil(reorder_point - inventory_position))
                if order_qty > 0:
                    suggestion = f"建议补货 {int(order_qty)} 件"
                    arrival_date = (date + timedelta(days=lead_time)).date()
                    incoming_deliveries[arrival_date] += order_qty
                else:
                    suggestion = "无需补货"
                status = "库存充足"
                if stock_after_sale <= 0:
                    status = "缺货"
                    # suggestion = f"建议补货"
                elif stock_after_sale < safety_stock_for_lead_time:
                    status = "库存紧张"
                    # suggestion = f"建议补货"

                daily_results.append({
                    'date': date.strftime('%Y-%m-%d'),
                    'predicted_demand': int(predicted_demand),                  #预测需求
                    'only_return_num': round(predicted_demand*only_return_rate),  #仅退款数
                    'real_demand': round(real_demand),                          #实际需求
                    'stock_before': int(np.round(stock_before_sale)),           #售前库存
                    'stock_after': int(np.round(stock_after_sale)),             #售后库存
                    'safety_stock': int(np.round(safety_stock_for_lead_time)),
                    'reorder_point': int(np.round(reorder_point)),
                    'status': status,
                    'suggestion': suggestion,
                    'order_qty': int(order_qty),
                    'delivery_arrived': int(delivery_arrived),
                    'return_generated': int(np.round(return_qty)),
                    'return_arrived': int(return_goods),
                    'phase': current_phase,
                    'phase_name': phase_names[current_phase],
                    'stock_on_order':int(stock_on_order)
                })
                stock_level = stock_after_sale
                # ====== 新增：计算阶段需求占比 ======
                total_demand = sum(phase_demand.values())
                phase_percentages = {}
                for phase, demand in phase_demand.items():
                    if total_demand > 0:
                        percentage = (demand / total_demand) * 100
                        phase_percentages[f'{phase}_percentage'] = round(percentage, 1)
                    else:
                        phase_percentages[f'{phase}_percentage'] = 0.0
                # ====== 阶段需求统计结束 ======
            summary = {
                'total_demand': int(sum(d['predicted_demand'] for d in daily_results)),
                'total_orders': int(sum(d['order_qty'] for d in daily_results)),
                'model_type': '单商品Prophet模型',
                'product_name': product_info['商品名称'],
                'product_id': product_info['商品ID']
            }
            # 评估指标
            try:
                from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
                hist_pred = forecast.loc[:len(df)-1, 'yhat']
                hist_true = df['y'].values
                r2 = r2_score(hist_true, hist_pred)
                mae = mean_absolute_error(hist_true, hist_pred)
                rmse = mean_squared_error(hist_true, hist_pred, squared=False)
                mape = (abs((hist_true - hist_pred) / (hist_true + 1e-8))).mean() * 100
                metrics = {'R2': round(r2,4), 'MAE': round(mae,2), 'RMSE': round(rmse,2), 'MAPE': f'{mape:.2f}%'}
            except Exception:
                metrics = None
            # 生成趋势图
            # print("预测的数据",forecast)
            try:
                matplotlib.use('Agg')
                matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'Microsoft YaHei']
                matplotlib.rcParams['axes.unicode_minus'] = False

                fig, ax = plt.subplots(figsize=(10,3))
                ax.plot(df_copy['ds'], df_copy['y'], label='历史销量', marker='o')
                ax.plot(forecast['ds'].iloc[:days], forecast['yhat'].iloc[:days], label='预测', color='#667eea')
                ax.fill_between(forecast['ds'].iloc[:days], forecast['yhat_lower'].iloc[:days],
                                forecast['yhat_upper'].iloc[:days], color='#c3cfe2', alpha=0.3, label='置信区间')
                ax.legend()
                ax.set_title('历史与未来销量趋势')
                ax.set_xlabel('日期')
                ax.set_ylabel('销量')
                fig.tight_layout()
                buf = io.BytesIO()
                plt.savefig(buf, format='png')
                buf.seek(0)
                plot_url = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('utf-8')
                plt.close(fig)
            except Exception as e:
                plot_url = None
                print(f'生成图表失败: {str(e)}')
            show_result = True
            uploaded_filename = session.get('uploaded_file_display') if 'uploaded_file_display' in session else None
            
            # 保存结果到session用于导出
            session['single_predict_table'] = daily_results
            session['single_predict_summary'] = summary
            session['single_predict_product_info'] = product_info
            session['single_predict_params'] = params
            
            # 将图表保存到临时文件，避免session过大
            if plot_url and plot_url.startswith('data:image/png;base64,'):
                try:
                    from PIL import Image
                    
                    # 从base64数据中提取图片数据
                    image_data = base64.b64decode(plot_url.split(',')[1])
                    
                    # 创建图片对象
                    img = Image.open(io.BytesIO(image_data))
                    
                    # 保存到临时文件
                    temp_dir = os.path.join(os.path.dirname(__file__), '../uploads')
                    os.makedirs(temp_dir, exist_ok=True)
                    temp_img_path = os.path.join(temp_dir, f'chart_{product_info.get("商品ID", "unknown")}_{int(time.time())}.png')
                    img.save(temp_img_path)
                    
                    # 只保存文件路径到session
                    session['single_predict_plot_path'] = temp_img_path
                    print(f"图表已保存到: {temp_img_path}")
                    
                except Exception as e:
                    print(f"保存图表失败: {e}")
                    session['single_predict_plot_path'] = None
            else:
                session['single_predict_plot_path'] = None
            
            return render_template('single_predict_analysis.html',
                             product_info=product_info, table=daily_results, metrics=metrics,
                              plot_url=plot_url, params=params, show_result=show_result, summary=summary,
                              uploaded_filename=uploaded_filename)
        except Exception as e:
            flash(f'模型训练或预测失败: {e}')
            return redirect(request.url)
    return render_template('single_predict_analysis.html',
                             product_info=product_info, table=daily_results, metrics=metrics,
                              plot_url=plot_url, params=params, show_result=show_result, summary=summary,
                              uploaded_filename=display_filename)

def get_product_id(file_name):
    match = re.search(r'(\d{12})', file_name)
    if match:
        # print(f"提取的商品ID: {match.group(1)}")  # 输出: 12345
        return  match.group(1)
    else:
        return "未找到商品ID"

def get_item_id(file_name):
    """
    从文件名中提取货号（固定7位，由大写字母和数字组成）
    """
    match = re.search(r'(?:^|[^A-Z0-9])([A-Z]\d{6})(?:$|[^A-Z0-9])', file_name)
    if match:
            return match.group(1)
    else:
        return "未找到货号"
#数据预处理
def preprocess_data(df):
    # 1. 日期格式处理
    try:
        # 尝试自动解析日期
        s1 = pd.to_datetime(df['ds'], format='%Y年%m月%d日', errors='coerce')
        s2 = pd.to_datetime(df['ds'], format='%Y-%m-%d', errors='coerce')
        df['ds'] = s1.fillna(s2)

        # 如果全部解析失败，尝试中文格式转ISO格式
        if df['ds'].isna().all():
            def chinese_date_to_iso(s):
                m = re.match(r'(\d{4})年(\d{1,2})月(\d{1,2})日', str(s))
                if m:
                    return f"{m.group(1)}-{int(m.group(2)):02d}-{int(m.group(3)):02d}"
                return s

            df['ds'] = df['ds'].apply(chinese_date_to_iso)
            df['ds'] = pd.to_datetime(df['ds'], errors='coerce')
            # 如果全部解析失败，尝试中文格式转ISO格式
        # elif df['ds'].isna().all():
        #     def english_date_to_iso(s):
        #         m = re.match(r'(\d{4})-(\d{1,2})-(\d{1,2})', str(s))
        #         if m:
        #             return f"{m.group(1)}-{int(m.group(2)):02d}-{int(m.group(3)):02d}"
        #         return s
        #
        #     df['ds'] = df['ds'].apply(english_date_to_iso)
        #     df['ds'] = pd.to_datetime(df['ds'], errors='coerce')

        # 移除无效日期
        df = df.dropna(subset=['ds'])
        df = df.sort_values('ds')

        # 检查日期连续性
        date_diff = df['ds'].diff().dt.days
        if any(date_diff[1:] > 1):  # 跳过第一个NaN
            flash('警告：日期存在间隔，可能导致预测偏差')

    except Exception as e:
        flash(f'日期处理失败: {e}')
        return None

    # 2. 数值处理
    try:
        # 转换数值类型
        df['y'] = pd.to_numeric(df['y'], errors='coerce')

        # 检查数据量
        if len(df) < 1:
            flash('数据量过少，建议上传10条以上的连续历史数据')
            return None
    except Exception as e:
        flash(f'数值处理失败: {e}')
        return None

    # 3. 异常值检测与处理
    try:
        # 方法1: IQR(四分位距)方法检测静态异常值
        Q1 = df['y'].quantile(0.25)
        Q3 = df['y'].quantile(0.75)
        IQR = Q3 - Q1
        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR

        # 方法2: 基于移动标准差检测时间序列异常值
        window_size = max(3, min(7, len(df) // 4))  # 动态窗口大小
        df['rolling_mean'] = df['y'].rolling(window=window_size, min_periods=1).mean()
        df['rolling_std'] = df['y'].rolling(window=window_size, min_periods=1).std().fillna(0)
        std_threshold = 2.5  # 标准差倍数阈值

        # 识别异常值
        df['is_outlier'] = False
        df['is_outlier'] |= (df['y'] < lower_bound) | (df['y'] > upper_bound)
        df['is_outlier'] |= (abs(df['y'] - df['rolling_mean']) > std_threshold * df['rolling_std'])

        # 处理异常值 (保留原始数据，创建新列处理)
        df['y_processed'] = df['y']

        # 对于异常值，使用线性插值或前后均值替代
        for i in range(len(df)):
            if df.iloc[i]['is_outlier']:
                # 尝试使用前后点插值
                if i > 0 and i < len(df) - 1:
                    prev_val = df.iloc[i - 1]['y_processed']
                    next_val = df.iloc[i + 1]['y_processed']
                    df.at[i, 'y_processed'] = (prev_val + next_val) / 2
                # 如果首尾点异常，使用最近的非异常值
                elif i == 0 and len(df) > 1:
                    df.at[i, 'y_processed'] = df.iloc[1]['y_processed']
                elif i == len(df) - 1 and len(df) > 1:
                    df.at[i, 'y_processed'] = df.iloc[-2]['y_processed']

        # 记录异常值数量
        outlier_count = df['is_outlier'].sum()
        if outlier_count > 0:
            flash(f'检测到{outlier_count}个异常值已处理')

        # 移除临时列
        df = df.drop(columns=['rolling_mean', 'rolling_std', 'is_outlier'])

        return df

    except Exception as e:
        flash(f'异常值处理失败: {e}')
        return df[['ds', 'y']]  # 返回原始数据作为后备


def date_none(date_input):
    # date_str = request.form.get(date_input, '')
    date_input_str = date_input
    if not date_input_str:
        date_input = datetime.strptime('2025-07-01', '%Y-%m-%d')
    else:
        date_input = datetime.strptime(date_input_str, '%Y-%m-%d')
    return date_input

